<?php
namespace app\common\libs;

use WebWorker\Libs\Mdb;
use WebWorker\Libs\Mredis;

class Model{

    /**
     * 数据库实例
     * @var \WebWorker\Libs\Mpdo
     */
    protected $db = null;

    /**
     * redis实例
     * @var \Redis
     */
    protected $redis = null;

    /**
     * 数据库配置名
     * @var string
     */
    protected $dbConfig = 'db';

    /**
     * redis配置名
     * @var string
     */
    protected $redisConfig = 'redis';

    /**
     * 表名，不带前缀
     * @var string
     */
    protected $tableName = '';

    /**
     * 表前缀
     * @var string
     */
    protected $tablePrefix = '';

    /**
     * 表主键
     * @var string
     */
    protected $pk = 'id';

    /**
     * sql参数
     * @var array
     */
    protected $options = [];

    /**
     * 参数化绑定的值
     * @var null
     */
    protected $sqlBind = null;

    public function __construct(){
        try{
            $config = $GLOBALS['config'][getProjectName(debug_backtrace())];
            $this->fileHost = isset($config['file_host'])?trim($config['file_host'],'/'):'';
            $this->tablePrefix = $config[$this->dbConfig]['prefix'];
            $this->tableName = $this->getTableName();
            $this->db = Mdb::getInstance($config[$this->dbConfig]);
            $this->redis = Mredis::getInstance($config[$this->redisConfig]);
        }catch (\Exception $e){
            echo "error:{$e->getMessage()}\n";
        }
    }

    /**
     * 获取当前模型名称
     * @return bool|string
     */
    protected function getModelName(){
        $class = get_class($this);
        return substr($class, strrpos($class,'\\') + 1);
    }

    /**
     * 获取当前模型对应的表名
     * @return string
     */
    protected function getTableName(){
        if($this->tableName) return $this->tableName;
        return parseStr($this->getModelName());
    }

    /**
     * 设置去重
     * @return $this
     */
    public function distinct(){
        $this->options['distinct'] = true;
        return $this;
    }

    /**
     * 设置查询字段
     * @param string $field 字段 eg:'id,name,age'
     * @return $this
     */
    public function field(string $field){
        $this->options['field'] = $field;
        return $this;
    }

    /**
     * 设置别名
     * @param string $alias 名称
     * @return $this
     */
    public function alias(string $alias){
        $this->options['alias'] = $alias;
        return $this;
    }

    /**
     * 设置关联表
     * @param string|array $join 关联语句 eg:'join table as t2 on t.fid=t1.id';
     * @return $this
     */
    public function join($join){
        is_array($join) && $join = implode(' ', $join);
        $this->options['join'] = $join;
        return $this;
    }

    /**
     * 设置查询条件
     * @param string|array $where
     *  eg:"name='value'"、['name=? and name2=?',['value','value2']]、['name=:name',[’:name‘=>'value']]
     * @return $this
     */
    public function where($where){
        $bind = null;
        if(is_array($where)){
            isset($where[1]) && $bind = $where[1];
            isset($where[0]) && $where = $where[0];
        }else if(is_numeric($where)){
            $where = $this->pk . '=' . $where;
        }
        $this->options['where'] = $where;
        $this->sqlBind = $bind;
        return $this;
    }

    /**
     * 设置分组
     * @param string $group 分组字段
     * @return $this
     */
    public function group(string $group){
        $this->options['group'] = $group;
        return $this;
    }

    /**
     * 设置分组条件
     * @param string $having 条件
     * @return $this
     */
    public function having(string $having){
        $this->options['having'] = $having;
        return $this;
    }

    /**
     * 设置排序
     * @param string $order eg:'id desc,time desc'
     * @return $this
     */
    public function order(string $order){
        $this->options['order'] = $order;
        return $this;
    }

    /**
     * 设置限制结果条数
     * @param int $start 开始下标
     * @param int $length 长度
     * @return $this
     */
    public function limit(int $start=0, int $length=0){
        if($start > 0 && $length == 0){
            $this->options['limit'] = "$start";
        }else{
            $this->options['limit'] = "$start,$length";
        }
        return $this;
    }

    /**
     * 设置分页参数
     * @param int $totalCount 总数据数
     * @param int $length 每页数量
     * @return $this
     */
    public function page(int $totalCount, int $length){
        $current = isset($_GET['page']) ? $_GET['page'] : 1;
        $totalPage = ceil($totalCount / $length);
        $current > 1 && $current > $totalPage && $current = $totalPage;
        $start = ($current - 1) * $length;
        $this->limit($start, $length);
        $this->options['currentPage'] = $current;
        $this->options['totalPage'] = $totalPage;
        return $this;
    }

    /**
     * 不需要转枚举字段图等可转的图
     * @return $this
     */
    protected function nocon(){
        $this->options['nocon'] = true;
        return $this;
    }

    /**
     * 不需要转枚举字段图
     * @return $this
     */
    protected function noconMap(){
        $this->options['noconMap'] = true;
        return $this;
    }

    /**
     * 不需要转文件字段图
     * @return $this
     */
    protected function noconFileMap(){
        $this->options['noconFileMap'] = true;
        return $this;
    }

    /**
     * 不需要转时间戳字段图
     * @return $this
     */
    protected function noconTimestampMap(){
        $this->options['noconTimestampMap'] = true;
        return $this;
    }

    public function lock($lock=false){
        $this->options['lock'] = $lock;
        return $this;
    }

    public function fetchSql(){
        $this->options['fetchSql'] = true;
        return $this;
    }

    /**
     * 设置参数
     * @param array $options
     * @return $this
     */
    public function options(array $options){
        $this->options = $options;
        return $this;
    }

    /**
     * 获取参数
     * @param string $name
     * @return null
     */
    public function getOptions(string $name=''){
        if ('' === $name) {
            return $this->options;
        } else {
            return isset($this->options[$name]) ? $this->options[$name] : null;
        }
    }

    /**
     * 查询数量
     * @return mixed
     */
    public function count(){
        $data = $this->field('count(*) as count')->nocon()->find();
        return isset($data['count']) ? $data['count'] : $data;
    }

    /**
     * 查询单条数据
     * @param int $id 主键值
     * @return mixed
     */
    public function find(int $id=0){
        if($id > 0) $this->options['where'] = $this->fieldQuote($this->pk) . " = $id";
        $this->limit(1);
        return $this->db->find($this->assemblySql('select'), $this->sqlBind);
    }

    /**
     * 查询多条数据
     * @return array
     */
    public function select(){
        return $this->db->select($this->assemblySql('select'), $this->sqlBind);
    }

    /**
     * 分页查询多条数据
     * @param int $length 每页数量
     * @return array
     */
    public function paginate(int $length){
        (empty($length) || !is_numeric($length)) && $length = 10;
        if($length > 0){
            $options = $this->getOptions();
            unset($this->options['order']);
            $records = $this->count();
            $this->options($options);
            $this->page($records, $length);
        }else{
            $records = null;
        }
        return ['page'=>$this->getOptions('currentPage'),
            'total'=>$this->getOptions('totalPage'), 'records'=>$records, 'rows'=>$this->select()];
    }

    /**
     * 添加数据
     * @param array $data eg:[’name‘=>'名字']
     * @return array
     */
    public function add(array $data){
        if(empty($data)) return error('$data不能为空');
        $data = $this->convertToSave($data);
        if($data === false) return error('字段值未在枚举字段图定义');
        $fields = implode(',', array_map(array($this, 'fieldQuote'), array_keys($data)));
        $values = implode(',', array_map(array($this->db, 'quote'), $data));
        $id = $this->db->add($this->assemblySql('insert', $fields, $values));
        return success($id);
    }

    /**
     * 批量添加数据
     * @param array $dataSet
     * @return array
     */
    public function addAll(array $dataSet){
        $fields = '*';
        $values = '';
        foreach($dataSet as $data){
            $data = $this->convertToSave($data);
            if($data === false) return error('字段值未在枚举字段图定义');
            empty($values) && $fields = implode(',', array_map(array($this, 'fieldQuote'), array_keys($data)));
            $values .= implode(',', array_map(array($this->db, 'quote'), $data)) . '),(';
        }
        $values = trim($values, '),(');
        $id = $this->db->add($this->assemblySql('insert', $fields, $values));
        return success($id);
    }

    /**
     * 更新数据
     * @param array $data 要更新的数据 eg:[’name‘=>'名字']
     * @return array
     */
    public function update(array $data){
        if(empty($data)) return error('$data不能为空');
        $data = $this->convertToSave($data);
        if($data === false) return error('字段值未在枚举字段图定义');
        array_walk($data, function(&$v, $k){
            $v = $this->fieldQuote($k) . ' = ' . $this->db->quote($v);
        });
        if(isset($data[$this->pk])) unset($data[$this->pk]);
        $sets = implode(',', $data);
        $count = $this->db->update($this->assemblySql('update', $sets), $this->sqlBind);
        if($count > 0 ) return success($count);
        return error('没有数据被更新');
    }

    /**
     * 软删除
     * @param int $id 主键值
     * @return array
     */
    public function softDelete(int $id=0){
        if($id > 0) $this->where($this->fieldQuote($this->pk) . " = $id");
        return $this->update(['deleted_at' => time()]);
    }

    /**
     * 删除数据
     * @param int $id 主键值
     * @return mixed
     */
    public function delete(int $id=0){
        if($id > 0) $this->where($this->fieldQuote($this->pk) . " = $id");
        $count = $this->db->update($this->assemblySql('delete'), $this->sqlBind);
        if($count > 0 ) return success($count);
        return error('没有数据被删除');
    }

    /**
     * 自增
     * @param string $field 字段
     * @param int $step 增加的幅度
     * @return array
     */
    public function setInc(string $field, int $step = 1){
        return $this->setIncOrDec($field, $step, '+');
    }

    /**
     * 自减
     * @param string $field 字段
     * @param int $step 减少的幅度
     * @return array
     */
    public function setDec(string $field, int $step = 1){
        return $this->setIncOrDec($field, $step, '-');
    }

    /**
     * 自增或自减
     * @param string $field 字段
     * @param int $step 变化的幅度
     * @param string $c 运算符
     * @return array
     */
    private function setIncOrDec(string $field, int $step = 1, $c='+'){
        $sets = "$field = $field $c $step";
        $count = $this->db->update($this->assemblySql('update', $sets), $this->sqlBind);
        if($count > 0 ) return success($count);
        return error('没有数据被更新');
    }

    /**
     * 字段、表加反引号
     * @param string $field
     * @return string
     */
    private function fieldQuote(string $field){
        return "`$field`";
    }

    /**
     * 组装sql语句
     * @param string $type 增删改查
     * @param string $fields 更新或插入的字段
     * @param string $values 插入的值
     * @return string
     */
    private function assemblySql(string $type, string $fields='', string $values=''){
        $table = $this->fieldQuote($this->tablePrefix.$this->tableName);
        $where = isset($this->options['where']) ? "where ".$this->options['where'] : "";
        $lock = isset($this->options['lock']) && $this->options['lock'] ? 'for update' : '';
        if($type == 'select'){
            $dt = isset($this->options['distinct']) ? 'distinct' : '';
            $field = isset($this->options['field']) ? $this->options['field'] : '*';
            if(!isset($this->options['nocon'])){
                $field .= $this->convertToShow();
            }
            $alias = isset($this->options['alias']) ? "as ".$this->options['alias'] : "";
            $join = isset($this->options['join']) ? $this->options['join'] : "";
            $group = isset($this->options['group']) ? "group by ".$this->options['group'] : "";
            $having = isset($this->options['having']) ? "having ".$this->options['having'] : "";
            $order = isset($this->options['order']) ? "order by ".$this->options['order'] : "";
            $limit = isset($this->options['limit']) ? "limit ".$this->options['limit'] : "";
            $sql = "select $dt $field from $table $alias $join $where $group $having $order $limit $lock";
        }else if($type == 'update'){
            $sql = "update $table set $fields $where $lock";
        }else if($type == 'insert'){
            $sql = "insert into $table ($fields) values ($values)";
        }else if($type == 'delete'){
            $sql = "delete from $table $where $lock";
        }else{
            $sql = '';
        }
        if(isset($this->options['fetchSql'])) echo $sql;
        $this->clearSql();
        return $sql;
    }

    public function startTrans(){
        return $this->db->beginTransaction();
    }

    public function commit(){
        return $this->db->commit();
    }

    public function rollback(){
        return $this->db->rollBack();
    }

    /**
     * 获取并校验排序值
     * @param string $sort
     * @param string $default
     * @return string
     */
    protected function getOrderSort(string $sort, string $default='desc'){
        if(!in_array($default, ['asc', 'desc'])) $default = 'desc';
        if(empty($sort)) return $default;
        if(!in_array($sort, ['asc', 'desc'])) return $default;
        return $sort;
    }

    /**
     * 清除sql
     */
    private function clearSql(){
        $this->options = null;
    }

    /************分界线************/

    /**
     * 枚举字段图
     * @var array ['type'=>['inc'=>[1,'增加'], 'dec'=>[2,'减少'], ...], ...]
     */
    protected $map = [];

    /**
     * 时间戳字段图
     * @var array ['add_time','pay_time',...]
     */
    protected $timestampMap = [];

    /**
     * 文件字段图
     * @var array ['img_url','img_url'=>'default-url',...]
     */
    protected $fileMap = [];

    /**
     * 文件域名
     * @var string http://example.com
     */
    protected $fileHost = "";

    /**
     * 数据库存储格式根据枚举字段图转使用、显示格式
     * @return string
     */
    protected function convertToShow(){
        $str = '';
        $alias = isset($this->options['alias']) ? ($this->options['alias'].'.') : '';
        if(!isset($this->options['noconMap'])){
            foreach($this->map as $field=>$vs){
                if(empty($vs)) continue;
                $str1 = $str2 = '';
                foreach($vs as $key=>$v){
                    if(empty($v)) continue;
                    $str1 .= " WHEN ".$v[0]." THEN '$key'";
                    $str2 .= " WHEN ".$v[0]." THEN '".$v[1]."'";
                }
                $str .= ", CASE $alias`$field` $str1 END AS `$field`";
                $str .= ", CASE $alias`$field` $str2 END AS `{$field}_show`";
            }
        }
        if(!isset($this->options['noconTimestampMap'])) {
            foreach ($this->timestampMap as $field) {
                $af = "$alias`$field`";
                $str .= ", IF($af>1000000,FROM_UNIXTIME($af),$af) as `{$field}_show`";
            }
        }
        if(!isset($this->options['noconFileMap'])) {
            foreach ($this->fileMap as $k => $v) {
                $field = is_int($k) ? $v : $k;
                $default = is_int($k) ? '' : ($this->fileHost . ltrim($v, '.'));
                $af = "$alias`$field`";
                $str .= ", IF($af IS NULL OR $af='','$default',IF(LOCATE('http://',$af)=1 OR 
            LOCATE('https://',$af)=1,$af,CONCAT('$this->fileHost',TRIM(LEADING  '.' FROM $af)))) AS `{$field}_show`";
            }
        }
        return $str;
    }

    /**
     * 使用格式根据枚举字段图转数据库存储格式
     * @param array $data ['status'=>'none']
     * @return array
     * @throws \Exception
     */
    protected function convertToSave(array $data){
        if(empty($this->map)) return $data;
        foreach($data as $key=>$val){
            if(!isset($this->map[$key])) continue;
            if(is_int($val)) continue;
            $m = $this->map[$key];
            if(is_array($val)){
                foreach ($val as $v){
                    if(!isset($m[$v])){
                        throw new \Exception("枚举字段值错误，$v 在 $key 中没有定义");
                    }
                    $data[$key][] = $m[$v][0];
                }
            }else{
                if(!isset($m[$val])){
                    throw new \Exception("枚举字段值错误，$val 在 $key 中没有定义");
                }
                $data[$key] = $m[$val][0];
            }
        }
        return $data;
    }

    /**
     * 获取所有字段的枚举列表
     * @return array
     */
    public function getMaps(){
        $maps = [];
        foreach ($this->map as $k=>$vs){
            $maps[$k] = $this->getMap($k);
        }
        return $maps;
    }

    /**
     * 获取指定字段的枚举列表
     * @param string $field
     * @return array
     */
    public function getMap(string $field){
        if(!isset($this->map[$field])) return [];
        $list = [];
        foreach ($this->map[$field] as $f=>$v){
            $list[] = ['id'=>$f, 'name'=>$v[1]];
        }
        return $list;
    }

}